package com.dmx.net.ftp.impl;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.io.CopyStreamListener;
import org.apache.commons.net.io.Util;

import com.dmx.net.ConnectionException;
import com.dmx.net.CopyListenable;

public class FtpProgressClient extends FtpClient implements CopyListenable {

	private static Log log = LogFactory.getLog(FtpProgressClient.class);

	private CopyStreamListener copyStreamListener = null;

	private int copyBufferSize = 1024;

	private int localFileBufferSize = 80960;

	private int netFileBufferSize = 80960;

	public FtpProgressClient() {

	}

	public FtpProgressClient(CopyStreamListener Listener) {
		this.copyStreamListener = Listener;
	}

	public FtpProgressClient(CopyStreamListener Listener, int copyBufferSize,
			int localFileBufferSize, int netFileBufferSize) {

		copyStreamListener = Listener;
		this.copyBufferSize = copyBufferSize;
		this.localFileBufferSize = localFileBufferSize;
		this.netFileBufferSize = netFileBufferSize;
	}

	public boolean putFile(File localFile, String remoteFileName,
			boolean isResumed) throws ConnectionException, IOException {

		boolean isCompletedCmdOk = false;

		long bytesTransfered = getResourceSize(remoteFileName);

		if (!localFile.isFile() || !localFile.exists()) {
			throw new FileNotFoundException("File not exsited: ["
					+ localFile.getAbsolutePath() + "]");
		}

		InputStream fiStream = null;
		OutputStream remoteOuputStream = null;

		long streamSize = localFile.length();

		try {

			fiStream = new FileInputStream(localFile);
			fiStream = new BufferedInputStream(fiStream, localFileBufferSize);

			if (true == isResumed) {// resume upload
				if (bytesTransfered > localFile.length()) {
					throw new IOException(
							"Remote File existed, and bigger then the local src File");
				} else if (localFile.length() == bytesTransfered) {
					// If it's a resumed upload, we do nothing
					return true;
				}

				fiStream.skip(bytesTransfered);
			} else { // full upload

				FTPFile[] ftpFiles = ftpClient.listFiles(remoteFileName);

				if (ftpFiles.length > 0) {

					if (!ftpClient.deleteFile(remoteFileName)) {
						log.error("Failed to delete file\n"
								+ ftpClient.getReplyString());
						return false;
					}
				}
			}

			remoteOuputStream = ftpClient.appendFileStream(remoteFileName);
			remoteOuputStream = new BufferedOutputStream(remoteOuputStream,
					netFileBufferSize);

			Util.copyStream(fiStream, remoteOuputStream, copyBufferSize,
					streamSize, copyStreamListener, false);

		} finally {

			try {
				remoteOuputStream.close();
			} catch (Throwable t) {

			}

			try {
				fiStream.close();
			} catch (Throwable t) {

			}

			try {
				isCompletedCmdOk = ftpClient.completePendingCommand();
			} catch (Throwable t) {
				log.error("Failed to completePendingCommand", t);
			}
		}

		return isCompletedCmdOk;
	}

	public boolean getFile(File localFile, String remoteFileName,
			boolean isResumed) throws ConnectionException, IOException,
			RemoteFtpFileNotExistsException {

		this.testRemoteFileExists(remoteFileName);

		boolean isCompletedCmdOk = false;

		OutputStream foStream = null;
		InputStream remoteInputStream = null;

		long streamSize = getResourceSize(remoteFileName);
		long offSet = 0;

		if (0 == streamSize) {
			throw new IOException("File size is ZERO, will NOT retrive it ... ");
		}

		if (true == isResumed) {
			foStream = new FileOutputStream(localFile, true);
			offSet = localFile.length();
		} else {
			foStream = new FileOutputStream(localFile);
			offSet = 0;
		}

		foStream = new BufferedOutputStream(foStream, localFileBufferSize);

		try {
			if (offSet > 0) {
				ftpClient.setRestartOffset(offSet);
			}

			remoteInputStream = new BufferedInputStream(ftpClient
					.retrieveFileStream(remoteFileName), netFileBufferSize);

			Util.copyStream(remoteInputStream, foStream, copyBufferSize,
					streamSize, copyStreamListener, false);

		} finally {

			try {
				remoteInputStream.close();
			} catch (Throwable t) {

			}

			try {
				foStream.close();
			} catch (Throwable t) {

			}

			try {
				isCompletedCmdOk = ftpClient.completePendingCommand();
			} catch (Throwable t) {
				log.error("Failed to completePendingCommand", t);
			}
		}

		return isCompletedCmdOk;
	}

	public CopyStreamListener getCopyStreamListener() {
		return copyStreamListener;
	}

	public void setCopyStreamListener(CopyStreamListener copyStreamListener) {
		this.copyStreamListener = copyStreamListener;
	}

	public int getCopyBufferSize() {
		return copyBufferSize;
	}

	public void setCopyBufferSize(int copyBufferSize) {
		this.copyBufferSize = copyBufferSize;
	}

	public int getLocalFileBufferSize() {
		return localFileBufferSize;
	}

	public void setLocalFileBufferSize(int localFileBufferSize) {
		this.localFileBufferSize = localFileBufferSize;
	}

	public int getNetFileBufferSize() {
		return netFileBufferSize;
	}

	public void setNetFileBufferSize(int netFileBufferSize) {
		this.netFileBufferSize = netFileBufferSize;
	}

}
